package cn.thornbird.orgsync.service.impl;

import cn.thornbird.orgsync.entity.Company;
import cn.thornbird.orgsync.entity.Task;
import cn.thornbird.orgsync.entity.TaskDetail;
import cn.thornbird.orgsync.entity.User;
import cn.thornbird.orgsync.entity.status.ExecuteStatus;
import cn.thornbird.orgsync.entity.status.TaskFrequency;
import cn.thornbird.orgsync.exception.BadRequestException;
import cn.thornbird.orgsync.exception.ForbiddenException;
import cn.thornbird.orgsync.exception.NotFoundException;
import cn.thornbird.orgsync.helper.OrganizationSynchronization;
import cn.thornbird.orgsync.model.PageList;
import cn.thornbird.orgsync.model.UserData;
import cn.thornbird.orgsync.model.combined.TaskObject;
import cn.thornbird.orgsync.repository.CompanyRepository;
import cn.thornbird.orgsync.repository.TaskDetailRepository;
import cn.thornbird.orgsync.repository.TaskRepository;
import cn.thornbird.orgsync.repository.UserRepository;
import cn.thornbird.orgsync.repository.specification.TaskDetailSpecification;
import cn.thornbird.orgsync.repository.specification.TaskSpecification;
import cn.thornbird.orgsync.service.CertificateService;
import cn.thornbird.orgsync.service.TaskService;
import cn.thornbird.orgsync.util.DateUtil;
import cn.thornbird.orgsync.vo.record.TaskRecord;
import cn.thornbird.orgsync.vo.request.TaskDetailQueryRequest;
import cn.thornbird.orgsync.vo.request.TaskQueryRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
@AllArgsConstructor
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class TaskServiceImpl extends BaseService<TaskRepository, Task> implements TaskService {

    private final TaskRepository taskRepository;

    private final TaskDetailRepository taskDetailRepository;

    private final CompanyRepository companyRepository;

    private final UserRepository userRepository;

    private final CertificateService certificateService;

    private final StringRedisTemplate redisTemplate;

    @Override
    public TaskObject find(UserData userData, String id) throws ForbiddenException, NotFoundException {
        Task task = findFirstById(taskRepository, id);
        if (!userData.isSuperuser() && !task.getUserId().equals(userData.getId())) {
            throw new ForbiddenException("禁止操作，不是任务创建者");
        }
        return createTaskObject(task);
    }

    @Override
    public PageList<TaskDetail> getTaskDetails(UserData userData, String taskId,
                                               TaskDetailQueryRequest taskDetailQueryRequest,
                                               Integer pageNumber, Integer pageSize)
            throws ForbiddenException, NotFoundException {
        Task task = findFirstById(taskRepository, taskId);
        if (!userData.isSuperuser() && !task.getUserId().equals(userData.getId())) {
            throw new ForbiddenException("禁止操作，不是任务创建者");
        }
        boolean reverseOrder = taskDetailQueryRequest.getReverseOrder() == null ? true :
                taskDetailQueryRequest.getReverseOrder().booleanValue();
        Sort sort = Sort.by(reverseOrder ? Sort.Order.desc("planTime") : Sort.Order.asc("planTime"),
                Sort.Order.asc("sequenceIndex"));
        Pageable pageable = PageList.generatePageable(pageNumber, pageSize, sort);
        taskDetailQueryRequest.setTaskId(taskId);
        Page<TaskDetail> page = taskDetailRepository.findAll(new TaskDetailSpecification(taskDetailQueryRequest),
                pageable);
        return PageList.convertPage(page);
    }

    @Override
    public PageList<TaskObject> query(UserData userData, TaskQueryRequest taskQueryRequest,
                                      Integer pageNumber, Integer pageSize) {
        if (!userData.isSuperuser()) {
            taskQueryRequest.setUserId(userData.getId());
        }
        Sort sort = Sort.by(Sort.Order.desc("active"), Sort.Order.asc("userId"),
                Sort.Order.asc("createdAt"));
        Pageable pageable = PageList.generatePageable(pageNumber, pageSize, sort);
        Page<Task> page = taskRepository.findAll(new TaskSpecification(taskQueryRequest), pageable);
        PageList<TaskObject> pageList = PageList.createEmptyPageList(page);

        Map<String, User> userMap = new HashMap<>();
        Map<String, Company> companyMap = new HashMap<>();
        Set<String> userIds = new HashSet<>();
        Set<String> companyIds = new HashSet<>();
        for (Task task : page.getContent()) {
            userIds.add(task.getUserId());
            companyIds.add(task.getSourceCompanyId());
            companyIds.add(task.getTargetCompanyId());
        }
        if (!userIds.isEmpty()) {
            List<User> users = userRepository.findAllById(userIds);
            for (User user : users) {
                userMap.put(user.getId(), user);
            }
        }
        if (!companyIds.isEmpty()) {
            List<Company> companies = companyRepository.findAllById(companyIds);
            for (Company company : companies) {
                companyMap.put(company.getId(), company);
            }
        }

        for (Task task : page.getContent()) {
            TaskObject taskObject = createTaskObject(task, userMap, companyMap);
            pageList.getList().add(taskObject);
        }
        return pageList;
    }

    @Override
    public TaskObject save(UserData userData, TaskRecord taskRecord)
            throws BadRequestException, ForbiddenException, NotFoundException {
        Task task = null;
        if (userRepository.findFirstById(userData.getId()) == null) {
            throw new BadRequestException("用户不存在");
        }

        if (taskRecord.getId() != null) {
            task = findFirstById(taskRepository, taskRecord.getId());
            if (task == null) {
                throw new NotFoundException("任务不存在");
            }
            taskRecord.setUserId(task.getUserId());
        }
        if (!userData.hasAuthority(UserData.ADMIN)) {
            if (userData.hasAuthority(UserData.AUTHORIZED)) {
                if (!userData.getId().equals(taskRecord.getUserId())) {
                    throw new ForbiddenException("禁止操作，不是任务创建者");
                }
            } else {
                throw new ForbiddenException("禁止操作，没有权限");
            }
        }

        if (taskRecord.getId() == null) {
            if (companyRepository.findFirstById(taskRecord.getSourceCompanyId()) == null) {
                throw new BadRequestException("来源公司不存在");
            } else if (companyRepository.findFirstById(taskRecord.getTargetCompanyId()) == null) {
                throw new BadRequestException("目标公司不存在");
            }
        }

        if (taskRecord.getActive() == null) {
            taskRecord.setActive(false);
        }

        if (taskRecord.getId() == null) {
            task = new Task();
            taskRecord.setProperties(task);
            task.setBatchCount(0);
            task.setSuccessBatchCount(0);
            task.generateNewId();
        } else {
            taskRecord.setProperties(task, true);
        }
        taskRepository.save(task);
        return createTaskObject(task);
    }

    @Override
    public TaskObject delete(UserData userData, String id)
            throws BadRequestException, ForbiddenException, NotFoundException {
        Task task = findFirstById(taskRepository, id);
        if (task == null) {
            throw new NotFoundException("任务不存在");
        }

        long count = taskDetailRepository.countByTaskId(id);
        if (count > 0) {
            throw new BadRequestException("任务存在明细记录，不能删除");
        }

        if (!userData.hasAuthority(UserData.ADMIN)) {
            if (userData.hasAuthority(UserData.AUTHORIZED)) {
                if (!task.getUserId().equals(userData.getId())) {
                    throw new ForbiddenException("禁止操作，不是任务创建者");
                }
            } else {
                throw new ForbiddenException("禁止操作，没有权限");
            }
        }

        taskRepository.logicDelete(id);
        return createTaskObject(task);
    }

    @Override
    public void generateTaskDetails() {
        Date now = new Date();
        Date today = DateUtil.createDate(now);
        List<Task> tasks = taskRepository.findAllByActive(true);
        int total = 0;
        log.info("开始生成任务明细");

        List<TaskDetail> taskDetails = null;
        for (Task task : tasks) {
            Date currentPlanTime = null;
            if (task.getLastPlanTime() != null) {
                if (task.getLastExecuteStatus() != null) {
                    if (task.getFrequency() == TaskFrequency.Once) {
                        continue;
                    }
                    if (DateUtil.isSameDay(task.getLastPlanTime(), today)) {
                        continue;
                    }
                } else {
                    taskDetails = taskDetailRepository.findByTaskIdAndPlanTimeOrderBySequenceIndex(task.getId(),
                            task.getLastPlanTime());
                    if (taskDetails.size() == 0) {
                        continue;
                    }
                    TaskDetail taskDetail = taskDetails.get(taskDetails.size() - 1);
                    if (taskDetail.getExecuteStatus() != ExecuteStatus.Error) {
                        continue;
                    }
                    if (taskDetails.size() > task.getMaxRetryCount() || task.getRetryInterval() <= 0) {
                        continue;
                    }
                    if (DateUtil.minutesBetween(taskDetail.getEndTime(), now) < task.getRetryInterval()) {
                        continue;
                    }
                    currentPlanTime = task.getLastPlanTime();
                }
            }

            if (currentPlanTime == null) {
                if (!shouldExecuteToday(task, today)) {
                    continue;
                }

                if (task.getLastPlanTime() == null || task.getLastPlanTime().before(today)) {
                    Date executeTime = DateUtil.getDateWithTime(now, task.getHour(), task.getMinute(), -1, -1);
                    if (now.getTime() < executeTime.getTime()) {
                        continue;
                    }
                }
            }

            currentPlanTime = currentPlanTime == null ? today : currentPlanTime;
            TaskDetail taskDetail = new TaskDetail();
            taskDetail.generateNewId();
            taskDetail.setTaskId(task.getId());
            taskDetail.setPlanTime(currentPlanTime);
            taskDetail.setSequenceIndex((short) (taskDetails == null ? 1 : taskDetails.size() + 1));
            taskDetail.setExecuteStatus(ExecuteStatus.Created);
            taskDetailRepository.save(taskDetail);
            if (!DateUtil.isSameDay(task.getLastPlanTime(), currentPlanTime)) {
                task.setLastPlanTime(currentPlanTime);
                task.setLastExecuteStatus(null);
                task.setLastRetryCount(null);
                task.setLastStartTime(null);
                task.setLastEndTime(null);
                taskRepository.save(task);
            }
            log.info("任务明细已创建: {} / {}", taskDetail.getId(), taskDetail.getTaskId());
            total++;
        }
        log.info("任务明细生成结束, 共生成{}条记录", total);
    }

    @Override
    public void runTaskDetails() {
        List<TaskDetail> taskDetails = taskDetailRepository.findByExecuteStatusOrderByCreatedAt(ExecuteStatus.Created);
        for (TaskDetail taskDetail : taskDetails) {
            taskDetail.setExecuteStatus(ExecuteStatus.Started);
            taskDetail.setStartTime(new Date());
            taskDetailRepository.save(taskDetail);
            new Thread(new TaskRunner(taskDetail.getId())).start();
        }
    }

    private TaskObject createTaskObject(Task task) {
        return createTaskObject(task, null, null);
    }

    private TaskObject createTaskObject(Task task, Map<String, User> userMap, Map<String, Company> companyMap) {
        TaskObject taskObject = new TaskObject();
        taskObject.setTask(task);
        if (userMap != null) {
            taskObject.setUser(userMap.containsKey(task.getUserId()) ? userMap.get(task.getUserId()) : null);
        }
        if (companyMap != null) {
            taskObject.setTargetCompany(companyMap.containsKey(task.getTargetCompanyId()) ?
                    companyMap.get(task.getTargetCompanyId()) : null);
            taskObject.setSourceCompany(companyMap.containsKey(task.getSourceCompanyId()) ?
                    companyMap.get(task.getSourceCompanyId()) : null);
        }
        if (taskObject.getUser() == null) {
            taskObject.setUser(userRepository.findFirstById(task.getUserId()));
        }
        if (taskObject.getSourceCompany() == null) {
            taskObject.setSourceCompany(companyRepository.findFirstById(task.getSourceCompanyId()));
        }
        if (taskObject.getTargetCompany() == null) {
            taskObject.setTargetCompany(companyRepository.findFirstById(task.getTargetCompanyId()));
        }
        return taskObject;
    }

    private boolean shouldExecuteToday(Task task, Date today) {
        boolean result = false;
        if (task.getFrequency() == TaskFrequency.Once) {
            if (DateUtil.isSameDay(task.getCreatedAt(), today) || task.getCreatedAt().before(today)) {
                result = true;
            }
        } else if (task.getFrequency() == TaskFrequency.Daily) {
            result = true;
        } else if (task.getFrequency() == TaskFrequency.Weekly) {
            result = DateUtil.getDayOfWeek(today) == task.getDayOfWeek();
        } else if (task.getFrequency() == TaskFrequency.Monthly) {
            int dayOfMonth = task.getDayOfMonth();
            if (dayOfMonth == 0) {
                dayOfMonth = 1;
            } if (dayOfMonth < 0) {
                Date date = DateUtil.addDays(DateUtil.getLastDayOfMonth(today), dayOfMonth + 1);
                dayOfMonth = DateUtil.getDayOfMonth(date);
            }
            result = DateUtil.getDayOfMonth(today) == dayOfMonth;
        }
        return result;
    }


    class TaskRunner implements Runnable {

        private String taskDetailId;

        public TaskRunner(String taskDetailId) {
            this.taskDetailId = taskDetailId;
        }

        @Override
        public void run() {
            TaskDetail taskDetail = taskDetailRepository.findFirstById(taskDetailId);
            Task task = taskRepository.findFirstById(taskDetail.getTaskId());
            OrganizationSynchronization organizationSynchronization = new OrganizationSynchronization(
                    certificateService, redisTemplate, createTaskObject(task));
            String message = "Starting task execution, Task ID: {}, Sequence Index: {}";
            log.info("开始执行任务, ID: {}, 序号: {}", task.getId(), taskDetail.getSequenceIndex());
            boolean success = organizationSynchronization.runTask();
            log.info("任务执行结束, ID: {}, 序号: {}, 结果: {}", task.getId(), taskDetail.getSequenceIndex(),
                    success ? "成功" : "失败");
            String log = organizationSynchronization.getLog();
            saveTaskDetail(success, log);
        }

        private void saveTaskDetail(boolean success, String log) {
            TaskDetail taskDetail = taskDetailRepository.findFirstById(taskDetailId);
            taskDetail.setExecuteStatus(success ? ExecuteStatus.Ended : ExecuteStatus.Error);
            taskDetail.setEndTime(new Date());
            taskDetail.setLog(log);
            taskDetailRepository.save(taskDetail);
            List<TaskDetail> taskDetails = taskDetailRepository.findByTaskIdAndPlanTimeOrderBySequenceIndex(
                    taskDetail.getTaskId(), taskDetail.getPlanTime());
            Task task = taskRepository.findFirstById(taskDetail.getTaskId());
            if (success || taskDetails.size() >= task.getMaxRetryCount() + 1) {
                task.setLastExecuteStatus(taskDetail.getExecuteStatus());
                task.setLastStartTime(taskDetails.get(0).getStartTime());
                task.setLastEndTime(taskDetail.getEndTime());
                task.setLastRetryCount((short) (taskDetails.size() - 1));
                task.setBatchCount(task.getBatchCount() + 1);
                if (success) {
                    task.setSuccessBatchCount(task.getSuccessBatchCount() + 1);
                }
                taskRepository.save(task);
            }
        }

    }

}
